package org.zhuzx.util;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.remoting.RemoteAccessException;
import org.springframework.validation.BindException;
import org.springframework.validation.BindingResult;
import org.springframework.validation.FieldError;
import org.springframework.web.HttpRequestMethodNotSupportedException;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ExceptionHandler;

import java.io.IOException;
import java.sql.SQLException;
import java.util.List;
import java.util.stream.Collectors;

/**
 * 通用的全局异常捕获器，可以供业务系统直接继承，子类自己用@RestControllerAdvice激活生效。
 * 默认使用本包中的Result对象格式返回，子类可以覆盖newFailResult方法，返回自定义的结果集封装格式。
 * @author zhuzx 2022-07-05
 */
public class CommonExceptionHandler {

	private static final Logger log = LoggerFactory.getLogger(CommonExceptionHandler.class);

	public CommonExceptionHandler() {
		log.info("全局异常捕获器{}已加载，捕获异常后统一返回{}对象。",
				getClass().getSimpleName(), newFailResult(null).getClass().getSimpleName());
	}

	/**调用方使用错误的http方法调用接口，导致报错Request method 'XXX' not supported，
	 * 这种只能等调用方自己修改为正确的method，我方记录异常也没用，返回提示即可。*/
	@ExceptionHandler(HttpRequestMethodNotSupportedException.class)
	public AbstractResult handle(HttpRequestMethodNotSupportedException e) {
		return newFailResult(e.getMessage());
	}

	/** 代码中主动抛出的业务异常，因为message中已经写了错误原因，不需要打印堆栈，直接返回错误提示。*/
	@ExceptionHandler(BizException.class)
	public AbstractResult handle(BizException e) {
		return newFailResult(e.getMessage());
	}

	/** 参数校验不通过，直接返回错误提示。*/
	@ExceptionHandler(MethodArgumentNotValidException.class)
	public AbstractResult handle(MethodArgumentNotValidException e) {
		return createResponse(e.getBindingResult());
	}

	/** 参数校验不通过，直接返回错误提示。*/
	@ExceptionHandler(BindException.class)
	public AbstractResult handle(BindException e) {
		return createResponse(e.getBindingResult());
	}

	/** 空指针的错误太过常见，并且e.getMessage()不会返回信息，所以使用固定的提示语覆盖。*/
	@ExceptionHandler(NullPointerException.class)
	public AbstractResult handle(NullPointerException e) {
		String prefix = "操作失败";
		if (GeneralUtil.isBlank(e.getMessage())) {
			prefix += "，执行过程中发现有数据缺失";
		}
		return createResponse(prefix, e);
	}

	/*--------分隔线--------以上是较特殊的异常，需要对提示信息做包装或者转换。--------分隔线--------*/
	/*--------分隔线--------以下是通用型异常，捕获器只在原始的错误信息前面加个类别前缀。--------分隔线--------*/

	@ExceptionHandler(SQLException.class)
	public AbstractResult handle(SQLException e) {
		return createResponse("数据库操作失败", e);
	}

	/** AbstractResult系列内部的断言机制会抛出这个异常 */
	@ExceptionHandler(RemoteAccessException.class)
	public AbstractResult handle(RemoteAccessException e) {
		return createResponse("远程调用失败", e);
	}

	@ExceptionHandler(IOException.class)
	public AbstractResult handle(IOException e) {
		return createResponse("网络/文件操作失败", e);
	}

	@ExceptionHandler(Exception.class)
	public AbstractResult handle(Exception e) {
		return createResponse("操作失败", e);
	}

	@ExceptionHandler(Throwable.class)
	public AbstractResult handle(Throwable e) {
		return createResponse("服务器错误", e);
	}

	/** 打印错误日志，并拼接错误信息返回。*/
	private AbstractResult createResponse(String prefix, Throwable e) {
		log.error(prefix + "：", e);
		return newFailResult(GeneralUtil.concatStringExcludeBlank("：", prefix, e.getMessage()));
	}

	/** 拼接校验错误详细信息返回，不打印日志。*/
	private AbstractResult createResponse(BindingResult bindingResult) {
		List<FieldError> fieldErrors = bindingResult.getFieldErrors();
		//fieldErrors里面不知为何把所有字段都重复了一遍，原因未深究，先按文本去重。
		String errMsg = String.join("；", fieldErrors.stream().map(
						item -> "[" + item.getField() + "]" + item.getDefaultMessage())
				.collect(Collectors.toSet()));
		return newFailResult(errMsg);
	}

	/**
	 * 子类可以覆盖，返回其它类型的AbstractResult。
	 */
	protected AbstractResult newFailResult(String errMsg) {
		return Result.newFailResult(errMsg);
	}

}